local resty_rsa = require "resty.rsa"

-- 请求参数不合法返回内容
local REQUEST_ERROR = "{\"code\": \"500\", \"msg\": \"request error\"}"
-- 网关出错返回内容
local GATEWAY_ERROR = "{\"code\": \"500\", \"msg\": \"gateway error\"}"
-- 加密数据的参数名
local REQUEST_PARAM_NAME = "_"
-- 时间戳的参数名
local TIMESTAMP_PARAM_NAME = "t";
-- 限制请求
local LIMIT_SECEND = 60
-- 秘钥的长度大小
local KEY_BITS = 1024
-- 数据加密分段大小
local BLOCK_SIZE = KEY_BITS / 8
-- 加密body请求头前缀 后面加真实的Content-Type
local HEADER_PREFIX = "encrypt/"

-- rsa秘钥
local RSA_PRIV_KEY = 
[[
-----BEGIN PRIVATE KEY-----
MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAMhegKAhqfg+0i+h
heAT31xnaXPB2m05wL8nWf/BoOHMa+w/WEQp9QoinQp5e4sG4f1Y5Yn82JQjh4kZ
V+lZD5xRtegKZBgmDfKfNfi5Cib9CkqL0zO3OakbUSk7XDucaLMZ4DCZN21poFI1
e78NiFcMZLiB4vO1V0rt58H6SyRRAgMBAAECgYEAkQmPE9qqXS6kGKRT8uqPoSSd
+ZPWF4BZnETQ6cfwO+IsMNt9egHhBRAfGujq260Ews2pgePLphe90SjOMPQtzlCM
pfHW56hd10+65TrERwPLQnTrg9EUMX7+GH7IYvR1La6LUfCtMFHAxk2fu/vBHTx3
426NibBWt4K8J0xHYJ0CQQD9P/EgoitqMn6O6h251mLWEIGecj3TWN6Cp37UE7j/
MTrS1kG0lX9WemqxMXCmMxMpxipxmIuM37y6lFhoKLofAkEAyouMJeVWsjYPjBKP
4tuJRCyXNY+q9Td4gVd5zHdpaexS/ASatC1y2SWiXXaCpMKCkRQ6vaO17SMCxWtJ
MKkzjwJAShPEEomdLWkrv94XZ96f9oHJiHFeSE38eDdKT/qc6Hib/kQR4CLCpqcU
QlR14QebmWKP076NQ13GtMTjv0P6fQJACNX7oC+YD6AyH28z3basD1BOrGR/FcF8
vU++nX/cFmXb3Oiqgw+0geqVYbRo0J03qvKR+XHp3tV3KnuarsfC2wJBAObcNe7c
QMWC4bBTzTBv0cTM00KEaCfFmjmKtcT5HwTLU/ThKHOYk2fIB84NRfZAVKzNMgzr
KTPzCgYSOghVmbc=
-----END PRIVATE KEY-----
]]



local function toHex(str, separator)
    return str:gsub('.', function(c)
        return string.format("%02X" .. (separator or ""), string.byte(c))
    end)
end

local function fromHex(hex)
    --滤掉分隔符
    local hex = hex:gsub("[%s%p]", ""):upper()
    return hex:gsub("%x%x", function(c)
        return string.char(tonumber(c, 16))
    end)
end

local function decryptHex(priv, block_size, hex_str)
    local hex = fromHex(hex_str)
    local decrypted_data = {}
    for i=1,hex:len(), block_size do
        local block_decrypt_data = priv:decrypt(hex:sub(i, i+block_size-1))
        if block_decrypt_data then
            table.insert(decrypted_data, block_decrypt_data)
        else
            return nil
        end
    end
    return table.concat(decrypted_data)
end


local function send_error(error_body)
    ngx.header["Content-Type"] = "application/json"
    ngx.say(error_body)
end


-- 创建rsa秘钥对象
local priv, err = resty_rsa:new({
    private_key = RSA_PRIV_KEY,
    key_type = resty_rsa.KEY_TYPE.PKCS8,
})
if not priv then
    -- 获取rsa对象失败，返回的内容
    send_error(REQUEST_ERROR)
    return
end

-- 解密请求参数
-- 默认参数名为 _
local encrypted_hex_str = ngx.req.get_uri_args(0)[REQUEST_PARAM_NAME]

if encrypted_hex_str then
    
    local request_args_str = decryptHex(priv, BLOCK_SIZE, encrypted_hex_str)
    -- 请求参数不合法
    if not request_args_str then
        ngx.log(ngx.ERR, "请求参数不合法")
        send_error(REQUEST_ERROR)
        return
    end

    -- 计算请求时间between_secend（秒），超出范围返回 request error
    -- 默认时间戳的参数名为 t
    local request_args = ngx.decode_args(request_args_str)
    local between_secend = ngx.time() - request_args[TIMESTAMP_PARAM_NAME]
    -- 当请求时间间隔大于xs，直接返回
    if between_secend > LIMIT_SECEND then
        ngx.log(ngx.ERR, "请求时间间隔大于"..LIMIT_SECEND.."s")
        send_error(REQUEST_ERROR)
        return
    end

    -- 重写请求的参数值
    ngx.req.set_uri_args(request_args)
else
    ngx.log(ngx.ERR, "找不到符合要求的请求参数")
    send_error(REQUEST_ERROR)
    return
end



--[[
解密body，前提需要配置[ lua_need_request_body on ]，在OpenResty里请求体数据总是先
被读入内存，但是为了减小内存占用，设定了一个限制：8KB（32位系统）16KB（64位系统），
超过这个值会存放到硬盘上，这个值可以通过 [ client_body_buffer_size ]改变。

通常来说，内存的速度比硬盘快得多，典型的空间换时间的场景，可以在节约前提下，尽量让数据在内存中。

下面的逻辑只会读取内存中的body，如果读取不到，不会在尝试读取文件来获取body，尽可能的配置
[ client_body_buffer_size ]的值可以容纳请求体

--]]
ngx.req.read_body()
-- 读取请求体中的数据，同步非阻塞
local data = ngx.req.get_body_data()
if data then 
    local decrypt_data = decryptHex(priv, BLOCK_SIZE, data)
    if decrypt_data then
        local conent_type = ngx.req.get_headers()["content_type"]
        if conent_type and conent_type:find(HEADER_PREFIX, 1, HEADER_PREFIX:len()+1) then
            local real_content_type = conent_type:sub(HEADER_PREFIX:len()+1)
            ngx.req.set_header("Content-Type", real_content_type)
            ngx.req.set_body_data(decrypt_data)
        end
    else
        ngx.log(ngx.ERR, "请求参数不合法")
        send_error(REQUEST_ERROR)
    end
end



